1 module hip.audio_decoding.audio;
2 public import hip.api.audio;
3 public import hip.api.data.audio;
4 
5 
6 
7 private const(char)* getNameFromEncoding(HipAudioEncoding encoding)
8 {
9     final switch(encoding) with(HipAudioEncoding)
10     {
11         case MOD: return "mod";
12         case XM: return "xm";
13         case FLAC:return "flac";
14         case MIDI:return "midi";
15         case MP3:return "mp3";
16         case OGG:return "ogg";
17         case WAV:return "wav";
18     }
19 }
20 
21 
22 
23 T[] monoToStereo(T)(T[] data)
24 {
25     T[] ret = new T[data.length*2];
26     for(size_t i = 0; i < data.length; i++)
27     {
28         ret[i*2]    = data[i];
29         ret[i*2+1]  = data[i];
30     }
31     return ret;
32 }
33 
34 T[][2] stereoToMonoSplit(T)(T[] data)
35 {
36     assert(data.length % 2 == 0, "Stereo to mono data must be divisible by 2");
37 
38     size_t len2 = cast(size_t)(data.length / 2);
39     T[][2] ret = [new T[len2], new T[len2]];
40 
41     for(size_t i = 0; i < data.length; i++)
42     {
43         ret[i%2][cast(size_t)(i/2)] = data[i];
44     }
45 
46     return ret;
47 }
48 
49 T[] stereoToMono(T)(T[] data)
50 {
51     assert(data.length % 2 == 0, "Stereo to mono data must be divisible by 2");
52     T[] ret = new T[cast(size_t)(data.length / 2)];
53 
54     size_t retIdx = 0;
55     for(size_t i = 0; i < data.length; i+= 2)
56     {
57         ret[retIdx++] = cast(T)(cast(float)((data[i] + data[i+1]) / 2));
58     }
59 
60     return ret;
61 }
62 
63 private abstract class AHipAudioDecoder : IHipAudioDecoder
64 {
65     float sampleRate;
66     int channels;
67     protected float duration;
68     protected size_t clipSize;
69     ubyte getClipChannels(){return cast(ubyte)channels;}
70     size_t getClipSize(){return clipSize;}
71     float getDuration(){return duration;}
72     uint getSamplerate(){return cast(uint)sampleRate;}
73     AudioConfig getAudioConfig(){return AudioConfig(getSamplerate, AudioFormat.init, getClipChannels, cast(int)getClipSize);}
74     bool resample(in ubyte[] data, HipAudioType type, uint outputSampleRate, uint outputChannels){return false;}
75     bool channelConversion(in ubyte[] data, ubyte from, ubyte to){return false;}
76 }
77 
78 
79 version(AudioFormatsDecoder)
80 {
81     private struct _AudioStreamImpl
82     {
83         void[] block;
84         void initialize()
85         {
86             import audioformats;
87             block = new void[AudioStream.sizeof];
88         }
89 
90         void openFromMemory(in ubyte[] inputData)
91         {
92             if(block == null)
93                 initialize();
94             import audioformats;
95             (cast(AudioStream*)block).openFromMemory(inputData);
96         }
97         long getLengthInFrames()
98         {
99             import audioformats;
100             return (cast(AudioStream*)block).getLengthInFrames();
101         }
102         int getNumChannels()
103         {
104             import audioformats;
105             return (cast(AudioStream*)block).getNumChannels();
106         }
107         float getSamplerate()
108         {
109             import audioformats;
110             return (cast(AudioStream*)block).getSamplerate();
111         }
112         int readSamplesFloat(float[] outputBuffer)
113         {
114             import audioformats;
115             return (cast(AudioStream*)block).readSamplesFloat(outputBuffer);
116         }
117         void cleanUp()
118         {
119             import audioformats;
120             if(block != null)
121             {
122                 AudioStream as = *(cast(AudioStream*)block); //Make it execute the destructor here
123                 destroy(block);
124                 block = null;
125             }
126         }
127     }
128 
129     class HipAudioFormatsDecoder : AHipAudioDecoder
130     {
131         ///This is where the compressed data will actually be stored and from where we get the decoded data
132         private _AudioStreamImpl input;
133         
134 
135         ///Intermediary buffer where the decoded buffer will be put.
136         protected float[] chunkBuffer;
137         
138         ///Probably this will be deprecated
139         protected uint chunkSize;
140 
141         ///This can hold the entire audio buffer or only enough for playing
142         float[] decodedBuffer;
143 
144 
145         /**
146         *   Will completely decode all the data.
147         *   Returns if the decode was successful.
148         *   The decoded data can be retrieved by calling `getClipData()`
149         */
150         bool decode(in ubyte[] data, HipAudioEncoding encoding, HipAudioType type,
151         void delegate(in ubyte[]) onSuccess, void delegate() onFailure)
152         {
153             import audioformats : audiostreamUnknownLength;
154             input.openFromMemory(cast(ubyte[])data);
155 
156             long lengthFrames = input.getLengthInFrames();
157             channels = input.getNumChannels();
158             sampleRate = input.getSamplerate();
159 
160             bool decodeSuccesful;
161 
162             if(lengthFrames == audiostreamUnknownLength) ///? Streamed audio
163             {
164                 uint bytesRead = 0;
165             
166                 decodedBuffer.length = audioConfigDefaultBufferSize;
167                 ubyte[] output = cast(ubyte[])decodedBuffer;
168                 startDecoding(data, output, audioConfigDefaultBufferSize, encoding);
169                 while((bytesRead = updateDecoding(output)) != 0)
170                 {
171                     bytesRead/= float.sizeof;
172                     decodedBuffer.length+= bytesRead;
173                     output = cast(ubyte[])decodedBuffer[$-bytesRead..$];
174                 }
175                 duration = (clipSize / channels) / sampleRate;
176             }
177             else
178             {
179                 duration = lengthFrames/cast(double)sampleRate;
180                 size_t bufferSize = cast(size_t)(lengthFrames*channels);
181                 decodedBuffer = new float[bufferSize];
182                 int bytesRead = input.readSamplesFloat(decodedBuffer);
183 
184                 clipSize = decodedBuffer.length*float.sizeof;
185                 decodeSuccesful = bytesRead == lengthFrames;
186             }
187             input.cleanUp();
188             decodeSuccesful ? onSuccess(getClipData) : onFailure();
189 
190             return decodeSuccesful;
191         }
192         uint startDecoding(in ubyte[] data, ubyte[] outputDecodedData, uint chunkSize, HipAudioEncoding encoding)
193         {
194             input.openFromMemory(cast(ubyte[])data);
195             channels = input.getNumChannels();
196             sampleRate = input.getSamplerate();
197 
198             chunkSize = (chunkSize / channels) / float.sizeof;
199             chunkBuffer = new float[chunkSize];
200             this.chunkSize = chunkSize;
201             
202             return updateDecoding(outputDecodedData);
203         }
204         uint updateDecoding(ubyte[] outputDecodedData)
205         {
206             import core.stdc.string:memcpy;
207             int framesRead = 0;
208             uint currentRead = 0;
209             do
210             {
211                 framesRead = input.readSamplesFloat(chunkBuffer);
212                 assert(framesRead * channels * float.sizeof < outputDecodedData.length, "Out of boundaries decoding");
213                 if(framesRead != 0)
214                     memcpy(outputDecodedData.ptr + currentRead,
215                     chunkBuffer.ptr, framesRead * channels * float.sizeof);
216                 
217                 currentRead += framesRead  * channels * float.sizeof;
218             } while(framesRead > 0 && currentRead <= chunkSize);
219             
220 
221             clipSize+= currentRead;
222             return currentRead;
223         }
224 
225         override AudioConfig getAudioConfig(){return AudioConfig(getSamplerate, AudioFormat.float32Little, getClipChannels, cast(int)getClipSize);}
226         
227 
228         void dispose(){input.cleanUp();}
229 
230         override bool resample(in ubyte[] data, HipAudioType type, uint outputSampleRate, uint outputChannels,
231         void delegate(in ubyte[]) onSuccess, void delegate())
232         {
233             version(none)
234             {
235                 static assert(false, "Incomplete implementation.");
236                 auto resampler = new HipAllResample(
237                     getClipData(),
238                     cast(int)sampleRate, 
239                     cast(int)outputSampleRate, 
240                     channels, 
241                     1
242                 );
243 
244                 float resampleRate = cast(float)outputSampleRate / cast(float)sampleRate;
245                 float[] resampledBuffer = new float[cast(ulong)(decodedBuffer.length * resampleRate)];
246                 resampledBuffer[] = 0;
247 
248                 do{
249 
250                     import std.stdio;
251                     writeln("Resampled!");
252                 }
253                 while(resampler.fillBuffer(resampledBuffer));
254 
255                 decodedBuffer = resampledBuffer;
256                 clipSize = decodedBuffer.length * float.sizeof;
257                     import std.stdio;
258                 writeln("Converted from ",cast(int)sampleRate, "Hz to ", outputSampleRate, "Hz");
259 
260             }
261             onSuccess(getClipData);
262 
263             return true;
264         }
265         override bool channelConversion(in ubyte[] data, ubyte from, ubyte to)
266         {
267             if(to == 2 && from == 1)
268             {
269                 decodedBuffer = monoToStereo!(float)(cast(float[])data);
270                 channels = to;
271                 clipSize*= 2;
272                 return true;
273             }
274             else if(to == 1 && from == 2)
275             {
276                 decodedBuffer = stereoToMono!(float)(cast(float[])data);
277                 channels = to;
278                 clipSize/= 2;
279                 return true;
280             }
281             return false;
282         }
283 
284         ubyte[] getClipData(){return cast(ubyte[])decodedBuffer;}
285     }
286 }
287 
288 version(WebAssembly)
289 {
290     import hip.wasm;
291 
292     private alias WasmAudioBuffer = size_t;
293     extern(C) WasmAudioBuffer WasmDecodeAudio(size_t length, void* ptr, JSDelegateType!(void) dg);
294     extern(C) size_t WasmGetClipChannels(WasmAudioBuffer);
295     extern(C) size_t WasmGetClipSize(WasmAudioBuffer);
296     extern(C) double WasmGetClipDuration(WasmAudioBuffer);
297     extern(C) float WasmGetClipSamplerate(WasmAudioBuffer);
298 
299     class HipWebAudioDecoder : AHipAudioDecoder
300     {
301 
302         WasmAudioBuffer buffer;
303 
304         bool decode(in ubyte[] data, HipAudioEncoding encoding, HipAudioType type,
305         void delegate(in ubyte[] data) onSuccess, void delegate() onFailure)
306         {
307             buffer = WasmDecodeAudio(data.length, cast(void*)data.ptr, sendJSDelegate!((WasmAudioBuffer buff)
308             {
309                 assert(buff == buffer, "Different object returned from audio decoded.");
310 
311                 channels = WasmGetClipChannels(buffer);
312                 clipSize = WasmGetClipSize(buffer);
313                 duration = WasmGetClipDuration(buffer);
314                 sampleRate = WasmGetClipSamplerate(buffer);
315                 onSuccess(getClipData);
316             }).tupleof);
317             return false;
318         }
319     
320         ///Unsupported at the moment 
321             uint startDecoding(in ubyte[] data, ubyte[] outputDecodedData, uint chunkSize, HipAudioEncoding encoding){return 0;}
322             uint updateDecoding(ubyte[] outputDecodedData){return 0;}
323         ///
324 
325         ///Returns the buffer handle.
326         ubyte[] getClipData()
327         {
328             ubyte* ptr = cast(ubyte*)&buffer;
329             return ptr[0..WasmAudioBuffer.sizeof];
330         }
331     
332         void dispose(){}
333     }
334 }
335 
336 
337 class HipNullAudioDecoder: IHipAudioDecoder
338 {
339     bool decode(in ubyte[] data, HipAudioEncoding encoding, HipAudioType type,
340     void delegate(in ubyte[]), void delegate()){return false;}
341 
342     bool resample(in ubyte[] data, HipAudioType type, uint outputSampleRate, uint outputChannels,
343     void delegate(in ubyte[]), void delegate()){return false;}
344 
345     bool channelConversion(in ubyte[] data, ubyte from, ubyte to){return false;}
346     uint startDecoding(in ubyte[] data, ubyte[] outputDecodedData, uint chunkSize, HipAudioEncoding encoding){return 0;}
347     uint updateDecoding(ubyte[] outputDecodedData){return 0;}
348     AudioConfig getAudioConfig(){return AudioConfig.init;}
349     ubyte[] getClipData(){return null;}
350     size_t getClipSize(){return 0;}
351     float getDuration(){return 0;}
352     void dispose(){}
353     uint getSamplerate(){return 0;}
354     ubyte getClipChannels(){return 0;}
355 }
356 
357 
358 version(none) //Buggy and not currently working
359 {
360     import hip.audio_decoding.resampler;
361     package class HipAllResample : ResamplingContext
362     {
363         enum BYTES_PER_LOAD = 4096;
364 
365         int resampledSamples = 0;
366         float[] decodedData;
367         
368         this(ubyte[] decodedData, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels)
369         {
370             super(new SampleControlFlags(), inputSampleRate, outputSampleRate, inputChannels, outputChannels);
371             this.decodedData = cast(float[])decodedData;
372         }
373         override void loadMoreSamples()  @trusted
374         {
375 
376             int toResample;
377             ///Do it BYTES_PER_LOAD as step
378             if(resampledSamples + BYTES_PER_LOAD <= decodedData.length )
379                 toResample = BYTES_PER_LOAD;
380             else //If it overflows, clamp it to the minimum
381                 toResample = cast(int)(decodedData.length) - resampledSamples;
382 
383 
384             resamplerDataLeft.dataIn = cast(float[])(decodedData[resampledSamples .. resampledSamples+toResample]);
385             resamplerDataRight.dataIn = cast(float[])(decodedData[resampledSamples .. resampledSamples+toResample]);
386             resampledSamples+= toResample;
387 
388         }
389     }
390 }
391 
392 version(WebAssembly)
393     alias HipAudioDecoder = HipWebAudioDecoder;
394 else version(AudioFormatsDecoder)
395     alias HipAudioDecoder = HipAudioFormatsDecoder;
396 else
397 {
398     alias HipAudioDecoder = HipNullAudioDecoder;
399     pragma(msg, "WARNING: Using NullAudioDecoder");
400 }